import TJavaLib.TList;
import TJavaLib.TString;
import TJavaLib.TPoint;

public class TSensor extends TNode {
	public static final byte FORWARDER = 0;
	public static final byte DESTINATION = 1;
	public static final byte REPLICATION = 2;
	public static final byte NORMAL = 3;
	
	private static long autoID = 0;
	
	private long ID;
	
	private TList record;
	private byte type;
	
	public TSensor(TSimulator sim) {
		super(TNode.SENSOR);
		
		simulator = sim;
		ID = autoID++;
		
		//For the simulation setup
		radio.setAddr("s"+String.valueOf(ID));
		type = NORMAL;
		
		//Recording
		record = new TList();
	}
	
	//----------------------------Recorded data-------------------------------------
	public void setType(byte t) { type = t; }
	public byte getType() { return type; }
	public TList getRecord() { return record; }
	
	public boolean inRecord(String fAddr) {
		boolean rs = false;
		for (int i=0; i<record.len(); i++) {
			String str = ((TRecord)record.get(i)).fAddr;
			if (str.equalsIgnoreCase(fAddr)) {
				rs = true;
				break;
			}
		}
		return rs;
	}
	
	//----------------------------For simulation users-----------------------------------------
	public void bootup() {
		addTaskInTime(TTask.HELLO_PROTOCOL, 10);
		//addTaskInTime(TTask.PRINT_NB_TABLE, 1000);
	}
	
	public void callback(byte taskName) {
		switch (taskName)	{
			case TTask.HELLO_PROTOCOL:
				//System.out.print("At time: "+simulator.getSimTime()+", ");
				//System.out.println(getAddr()+" broadcasts a HELLO packet");
				THelloPacket hpk = new THelloPacket(getAddr(), loc);
				send(hpk);
				
				//Continue to add this task for the next send
				//addTaskInTime(TTask.HELLO_PROTOCOL, 1000);
				break;
				
			case TTask.PRINT_NB_TABLE:
				System.out.print("At time: "+simulator.getSimTime()+", ");
				System.out.print(getAddr()+" neighbors: |");
				for (int i=0; i<nbAddrs.len(); i++) {
					String nbAddr = (String)nbAddrs.get(i);
					System.out.print(nbAddr+"|");
				}
				System.out.println();
				break;
				
			case TTask.BROADCAST:
				//System.out.println("At time: "+simulator.getSimTime());
				//System.out.println("    "+anno+" got callback for BROADCAST task");
				break;
			
			default: break;
		}
	}
	
	//------------------------------Networking--------------------------------------------------
	public void processPacket(TPacket packet) {
		switch (packet.getType()) {
			
		/*---------------------The HELLO Protocol--------------------------------------------*/		
			case TPacket.HELLO:
				THelloPacket hpk = (THelloPacket)packet;
				//System.out.println(getAddr()+" receives HELLO from "+hpk.sAddr);
				if (TUtil.strIn(nbAddrs, hpk.sAddr) < 0) {
					nbAddrs.enList(hpk.sAddr);
					nbCoors.enList(hpk.sCoor);
				}
				break;
		
		/*---------------------The GSMR algorithm--------------------------------------------*/		
			case TPacket.GSMR:
				TGSMRPacket pkGSMR = new TGSMRPacket((TGSMRPacket)packet);
				
				/*/Debug
				if ("s82".equalsIgnoreCase(getAddr()) && 
					"s58".equalsIgnoreCase(pkGSMR.fAddr)) {
					System.out.println(getAddr()+" receives from "+pkGSMR.fAddr);
					System.out.print("Final list at "+getAddr()+" is: |");	
					for (int i=0; i<pkGSMR.sgList.len(); i++) {
						sg_struct sg = (sg_struct)pkGSMR.sgList.get(i);
						for (int j=0; j<sg.finalAddrs.len(); j++)
							System.out.print((String)sg.finalAddrs.get(j)+"|");	
						System.out.println();
					}
				}
				//End Debug*/
				
				//List of responsible subgroups
				TList subGroupList = new TList();
				for (int i=0; i<pkGSMR.sgList.len(); i++) {
					sg_struct sg = (sg_struct)pkGSMR.sgList.get(i);
					
					//Debug
					//Check if I am one of the destinations, then announce
					if (TUtil.strIn(sg.finalAddrs, getAddr()) >= 0)
						System.out.println("DESTINATION "+getAddr()+" RECEIVES A PACKET DESTININED TO IT!");
					//End debug
					
					//Check if I am one of the relay nodes
					if (getAddr().equalsIgnoreCase(sg.rAddr))
						subGroupList.enList(sg);
				}

				if (subGroupList.len() > 0) {
					//Mark the sensor as forwarder
					if (type != DESTINATION)
						type = FORWARDER;
					for (int k=0; k<subGroupList.len(); k++) {
						sg_struct subGroup = (sg_struct)subGroupList.get(k);
						
						//Debug
						System.out.println("-----------------------------------------------");
						System.out.println("At "+getAddr()+":");
						System.out.println(getAddr()+" receives packet from "+pkGSMR.fAddr);
						//End Debug*/		
						
						//Exclude all immediate reachable destinations from subgroup
						subGroup.finalAddrs = new TList();
						for (int i=0; i<nbAddrs.len(); i++) {
							String nbAddr = (String)nbAddrs.get(i);
							int pos = TUtil.strIn(subGroup.dAddrs, nbAddr);
							if (pos >= 0) {
								subGroup.dAddrs.deList(pos);
								subGroup.dCoors.deList(pos);
								subGroup.finalAddrs.enList(nbAddr);
							}
						}
						//Debug
						System.out.print("finalAddrs: ");
						for (int i=0; i<subGroup.finalAddrs.len(); i++)
								System.out.print((String)subGroup.finalAddrs.get(i)+", ");
						System.out.println();
						//End Debug*/
						//Debug
						System.out.print("Remaining dAddrs: ");
						for (int i=0; i<subGroup.dAddrs.len(); i++)
								System.out.print((String)subGroup.dAddrs.get(i)+", ");
						System.out.println();
						//End Debug*/
					
						//Recalculate forwarding line for the subgroup as
						//some destinations have been removed
						if (subGroup.finalAddrs.len() > 0) {
							subGroup.varphi = calVarphi(subGroup.lastRepCoor, subGroup.dCoors);
							subGroup.d2des = d2(subGroup.dCoors);
						}

						double delta_app = 0.85;
						TGSMRPacket newPacket = new TGSMRPacket(new TList());
						newPacket.fAddr = getAddr();
						newPacket.sAddr = pkGSMR.sAddr;
						if (subGroup.dAddrs.len() > 0) {
							/*----------------Void Handling-----------------------------------*/
							//My advancement to the destinations
							double fwdAdv = 0;
							System.out.println("Calculate ADV for "+getAddr());
							double myAdv = calAdvance(loc, subGroup);
							//Advancement of my forwarder to destinations
							int myFwd = TUtil.strIn(nbAddrs, pkGSMR.fAddr);
							TPoint myFwdCoor = null;
							if (myFwd >= 0) {
								myFwdCoor = (TPoint)nbCoors.get(myFwd);
								//System.out.println("Calculate ADV for "+pkGSMR.fAddr);
								fwdAdv = calAdvance(myFwdCoor, subGroup);
								//If negative advancement
								if (myAdv > fwdAdv) {
									double angle2des = calVarphi(loc, subGroup.dCoors);
									//Calculate angle to the forwarder
									double angle2fwd = Math.atan2(myFwdCoor.y-loc.y, myFwdCoor.x-loc.x);
									if (angle2fwd < 0) angle2fwd += 2*Math.PI;
									//Calculate the blacklist sign
									double minEdge = angle2des;
									double maxEdge = angle2des + Math.PI;
									if (maxEdge >= 2*Math.PI) maxEdge -= 2*Math.PI;
									if (maxEdge < minEdge) {
										double tmp = maxEdge;
										maxEdge = minEdge;
										minEdge = tmp;
									}
									if (minEdge<angle2fwd && angle2fwd<maxEdge) subGroup.blSign = 1;
									else subGroup.blSign = -1;
								}
							}
							//If I have more than one neighbors, blacklist the forwarder
							if (nbAddrs.len() > 1)
								subGroup.blAddr = pkGSMR.fAddr; 
							//This forwarder is the only neighbor I have, resend the packet 
							//to the forwarder, telling him to blacklist me
							else 
								subGroup.blAddr = getAddr();
								
							//Debug
							System.out.println("My adv: "+TUtil.dF.format(myAdv).toString()
												+"; Fwd adv: "+TUtil.dF.format(fwdAdv).toString()
												+"; blAddr: "+subGroup.blAddr+"; blSign: "+subGroup.blSign);
							//End debug*/
							/*----------------End of Void Handling----------------------------*/
							
							//Check if I am also the replication node for my subgroup
							double phi_size = phiSize(subGroup.dCoors);
							double d = d2(subGroup.dCoors);
							if (phi_size >= 2*Math.asin(radio.getTX()/d/delta_app)) {
								//Mark the sensor as replicator
								type = REPLICATION;
								
								//Debug
								System.out.println(getAddr()+" is also a replicator");
								//End Debug*/
					
								//Perform GSGD algorithm
								TList sgCoorList = new TList();
								TList sgAddrList = new TList();
								while (subGroup.dCoors.len() > 1) {
									TList sgCoors = new TList();
									TList sgAddrs = new TList();
									int gsize = subGroup.dCoors.len();
									boolean found = false;
									int minpos = Integer.MAX_VALUE;
									int minsize = Integer.MAX_VALUE;
									while (!found && gsize>0) {
										//System.out.println("gsize: "+gsize);
										//Find minimum sg
										double minphi = Double.MAX_VALUE;
										TList minsg = null;
										int ngroup = 1;
										if (gsize < subGroup.dCoors.len())
											ngroup = subGroup.dCoors.len()-1;
										for (int i=0; i<ngroup; i++) {
											TList tmp = buildSG(subGroup.dCoors, i, gsize);
											double phisize = phiSize(tmp);
											if (minphi > phisize) {
												minphi = phisize;
												minsg = tmp;
												minpos = i;
												minsize = gsize;
											}
										}
							
										/*/Debug	
										System.out.print("minsg: ");
										for (int i=0; i<minsg.len(); i++) {
											TPoint mi = (TPoint)minsg.get(i);
											String mx = TUtil.dF.format(mi.x).toString();
											String my = TUtil.dF.format(mi.y).toString();
											System.out.print("("+mx+","+my+"); ");
										}
										System.out.println();
										//End Debug*/
					
										//Check the inequation (5)
										double dg = d2(minsg);
										/*/Debug
										System.out.println("dg: "+TUtil.dF.format(dg).toString());
										System.out.println("minphi: "+TUtil.dF.format(minphi).toString());
										//End of debug*/
										double comp = 0; 
										if (radio.getTX() < dg*delta_app)
											comp = 2*Math.asin(radio.getTX()/dg/delta_app);
										else
											comp = Math.PI/2;
										if (minphi < comp) {
											//System.out.println("Eq. 5 satisfied for gsize="+gsize);
											found = true;
											sgCoors = minsg;
										}
										gsize--;
									}
									//Add the found subgroup to sglist
									sgCoorList.enList(sgCoors);
									sgAddrs = buildSG(subGroup.dAddrs, minpos, minsize);
									sgAddrList.enList(sgAddrs);
						
									//Remove the found subgroup from the multicast group
									subGroup.dCoors = excludeSG(subGroup.dCoors, minpos, minsize);
									subGroup.dAddrs = excludeSG(subGroup.dAddrs, minpos, minsize);
								}
								if (subGroup.dCoors.len() == 1) {
									sgCoorList.enList(subGroup.dCoors);
									sgAddrList.enList(subGroup.dAddrs);
								}
					
								//Debug
								System.out.println("The group is divided into: ");
								for (int i=0; i<sgAddrList.len(); i++) {
									TList tmp = (TList)sgAddrList.get(i);
									System.out.print("Sub-group "+(i+1)+": |");
									for (int j=0; j<tmp.len(); j++)
										System.out.print((String)tmp.get(j)+"|");
									System.out.println();
								}
								//End Debug*/
			
								//Calculate the next relay node, varphi for each subgroup
								for (int i=0; i<sgCoorList.len(); i++) {
									sg_struct sg = new sg_struct();
									sg.dCoors = (TList)sgCoorList.get(i);
									sg.dAddrs = (TList)sgAddrList.get(i);
								
									sg.lastRepCoor = loc;
									sg.varphi = calVarphi(loc, sg.dCoors);								
									sg.d2des = d2(sg.dCoors);
									System.out.println("D2des: "+TUtil.dF.format(sg.d2des).toString());
												
									//Calculate relay node's address
									sg.rAddr = calRelayNode(sg);
						
									//Add this sg_struct to the newPacket
									newPacket.sgList.enList(sg);		
								}
								//Debug
								System.out.println("Routing info:");
								for (int i=0; i<newPacket.sgList.len(); i++) {
									System.out.print("Group "+(i+1)+": |");
									sg_struct sg = (sg_struct)newPacket.sgList.get(i);
									TList tmp = (TList)sg.dAddrs;
									for (int j=0; j<tmp.len(); j++)
										System.out.print((String)tmp.get(j)+"|");
									System.out.println(" has relay node "+sg.rAddr);
								}
								//End debug*/
							} else { 
								//Simply forward the packet to the next relay node that lies in the forwarding line
								
								//Calculate relay node's address
								subGroup.rAddr = calRelayNode(subGroup);
								System.out.println("Next relay node: "+subGroup.rAddr);

								//Add this sg_struct to the newPacket
								newPacket.sgList.enList(subGroup);
							}
						} else { //Dont specify relay, coz all destinations are reached from here
							subGroup.rAddr = null;
							newPacket.sgList.enList(subGroup);
						}
					
						//Forward the packet
						//System.out.println("Forwarder: "+newPacket.fAddr);
						send(newPacket);

						//Debug
						System.out.println("-----------------------------------------------");
						//End Debug*/
					}
				}
				break;
			
			/*---------------------The GMR algorithm--------------------------------------------*/
			//Here you place the implementation of the GMR protocol. All the belonging assisting 
			//functions will be placed after the assisting functions of the GSMR implementation
				
			default: break;
		}
	}
	
	/*--------------------------Assisting Functions of GSMR Implementation----------------*/

	private double calAdvance(TPoint loc, sg_struct sg) {
		//The distance between forwarder and last replication node
		double d = TPoint.dist(loc, sg.lastRepCoor);
		
		double phi = Math.atan2(loc.y-sg.lastRepCoor.y, loc.x-sg.lastRepCoor.x);
		if (phi < 0) phi += 2*Math.PI;
		double alpha = phi-sg.varphi;
		
		//Calculate the advancement in forwarding line
		double adv = d*Math.cos(alpha);
		
		/*/Debug
		System.out.println("Alpha: "+TUtil.dF.format(alpha).toString()
							+"; d2Rep: "+TUtil.dF.format(d).toString()
							+"; rep2Des: "+TUtil.dF.format(sg.d2des).toString()
							+"; Adv: "+TUtil.dF.format(adv).toString());
		//End debug*/
		
		return Math.abs(sg.d2des - Math.abs(adv));
	}

	private double d2(TList dCoors) {
		double dg = 0;
		for (int i=0; i<dCoors.len(); i++) {
			TPoint mi = (TPoint)dCoors.get(i);
			dg += TPoint.dist(loc, mi);
		}
		dg /= dCoors.len();
		return dg;
	}
	
	private String calRelayNode(sg_struct sg) {
		String rs = null;
		String minAddr = null;
		
		//Calculate angle to destinations
		double angle2des = calVarphi(loc, sg.dCoors);
		
		double dmin = Double.MAX_VALUE;
		double dcan = Double.MAX_VALUE;
		for (int i=0; i<nbCoors.len(); i++) {
			String nbAddr = (String)nbAddrs.get(i);
			
			//Only non-blacklist neihbors are considered
			if (!nbAddr.equalsIgnoreCase(sg.blAddr)) {
				TPoint p = (TPoint)nbCoors.get(i);
				
				//Calculate advancement of this neighbor
				double phinb = Math.atan2(p.y-sg.lastRepCoor.y, p.x-sg.lastRepCoor.x);
				if (phinb < 0) phinb += 2*Math.PI;
				double alphanb = phinb - sg.varphi;
				double d = TPoint.dist(sg.lastRepCoor, p);
				double nbAdv = Math.abs(sg.d2des - d*Math.cos(alphanb));
				double nb2line = Math.abs(d*Math.sin(alphanb));
				double closeness = nbAdv*nb2line;
				
				if (dmin > closeness) {
					dmin = closeness;
					minAddr = nbAddr;
				}
				
				//Only neighbors at non-blacklisted side are considered
				int sign = 0;
				double angle2nb = 0;
				double minEdge = 0;
				double maxEdge = 0;
				if (sg.blSign != 0) {
					angle2nb = Math.atan2(p.y-loc.y, p.x-loc.x);
					if (angle2nb < 0) angle2nb += 2*Math.PI;
					minEdge = angle2des;
					maxEdge = angle2des + Math.PI;
					if (maxEdge >= 2*Math.PI) maxEdge -= 2*Math.PI;
					if (maxEdge < minEdge) {
						double tmp = maxEdge;
						maxEdge = minEdge;
						minEdge = tmp;
					}
					if (minEdge<angle2nb && angle2nb<maxEdge) sign = 1;
					else sign = -1;
				}
				if (sign != sg.blSign || (sign==sg.blSign && sign==0)) {
					if (dcan > closeness) {
						dcan = closeness;
						rs = nbAddr;
					}
				}
				
				//Debug
				if ("s48".equalsIgnoreCase(getAddr()) ||
				"s82".equalsIgnoreCase(getAddr()) ||
				"s14".equalsIgnoreCase(getAddr()) ||
				"s70".equalsIgnoreCase(getAddr()) ||
				"s10".equalsIgnoreCase(getAddr())) {
					System.out.println("Neighbor: "+nbAddr
													+"; rep2des:"+TUtil.dF.format(sg.d2des).toString()
													+"; phinb:"+TUtil.dF.format(phinb).toString()
													+"; alphanb:"+TUtil.dF.format(alphanb).toString()
													+"; d2rep:"+TUtil.dF.format(TPoint.dist(sg.lastRepCoor, p)).toString()
													+"; rep2proj:"+TUtil.dF.format(d*Math.cos(alphanb)).toString()
													+"; nb2line:"+TUtil.dF.format(nb2line).toString()
													+"; nbAdv:"+TUtil.dF.format(nbAdv).toString()
													+"; closeness:"+TUtil.dF.format(closeness).toString());
					/*System.out.println("Angle2des:"+TUtil.dF.format(angle2des).toString()
													+"; Angle2nb:"+TUtil.dF.format(angle2nb).toString()
													+"; minEdge:"+TUtil.dF.format(minEdge).toString()
													+"; maxEdge:"+TUtil.dF.format(maxEdge).toString()
													+"; Sign:"+sign+"; rs:"+rs);*/
				}
				//End debug			
			}
		}
		
		//If no relay is found on the other side, assign rs to the most advanced 
		//candidate. And reset the blSign
		if (rs == null) {
			rs = minAddr;
			sg.blSign = 0;
		}
		
		return rs;
	}
	
	private double calVarphi(TPoint loc, TList dCoors) {
		double varphi = 0;
		for (int i=0; i<dCoors.len(); i++) {
			TPoint mi = (TPoint)dCoors.get(i);
			double phi = Math.atan2(mi.y-loc.y, mi.x-loc.x);
			if (phi < 0) phi += 2*Math.PI;
			varphi += phi;
		}
		varphi /= dCoors.len();
		return varphi;
	}
	
	private TList buildSG(TList group, int pos, int size) {
		int ndes = group.len();
		TList subgroup = new TList();
		int p = pos;
		for (int i=0; i<size; i++) {
			subgroup.enList(group.get(p));
			p++; if (p>=ndes) p=0;
		}
		return subgroup;
	}
	
	private TList excludeSG(TList group, int pos, int size) {
		int ndes = group.len();
		
		TList newgroup = new TList();
		int lEdge = pos;
		int rEdge = pos+size-1;
		if (rEdge >= ndes) rEdge -= ndes;
		for (int i=0; i<ndes; i++) {
			if (i<lEdge || i>rEdge)
				newgroup.enList(group.get(i));
		}
		return newgroup;
	}
	
	private double phiSize(TList sgCoor) {
		//Calculate the angles
		int ndes = sgCoor.len();
		double minphi = Double.MAX_VALUE;
		double maxphi = Double.MIN_VALUE;
		for (int i=0; i<ndes; i++) {
			TPoint mi = (TPoint)sgCoor.get(i);
			double phii = Math.atan2(mi.y-loc.y, mi.x-loc.x);
			if (phii < 0) phii += 2*Math.PI;
			if (minphi > phii) minphi = phii;
			if (maxphi < phii) maxphi = phii;
		}
		return Math.abs(maxphi-minphi);
	}
	
	/*-------------------------------End of GSMR Implementation-------------------------------*/

	
		
	/*--------------------------Assisting Functions of GMR Implementation---------------------*/
		
	/*--------------------------------End of GMR Implementation-------------------------------*/
}
